/*
 * Copyright (c) 2010-2011  Florian Forster  <ff at octo.it>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include "api/yajl_tree.h"
#include "api/yajl_parse.h"

#include "yajl_parser.h"

#if defined(_WIN32) || defined(WIN32)
#define snprintf sprintf_s
#endif

#define STATUS_CONTINUE 1
#define STATUS_ABORT    0

struct stack_elem_s;
typedef struct stack_elem_s stack_elem_t;
struct stack_elem_s {
    char *key;
    yajl_val value;
    stack_elem_t *next;
};

struct context_s {
    stack_elem_t *stack;
    yajl_val root;
    char *errbuf;
    size_t errbuf_size;
};
typedef struct context_s context_t;

#define RETURN_ERROR(ctx, retval, ...) {                                  \
        if ((ctx)->errbuf != NULL)                                      \
            snprintf ((ctx)->errbuf, (ctx)->errbuf_size, __VA_ARGS__);  \
        return (retval);                                                \
    }

static yajl_val value_alloc(yajl_type type) {
    yajl_val v;

    v = malloc(sizeof(*v));
    if (v == NULL) return (NULL);
    memset(v, 0, sizeof(*v));
    v->type = type;

    return (v);
}

static void yajl_object_free(yajl_val v) {
    size_t i;

    if (!YAJL_IS_OBJECT(v)) return;

    for (i = 0; i < v->u.object.len; i++) {
        free((char *) v->u.object.keys[i]);
        v->u.object.keys[i] = NULL;
        yajl_tree_free(v->u.object.values[i]);
        v->u.object.values[i] = NULL;
    }

    free((void *) v->u.object.keys);
    free(v->u.object.values);
    free(v);
}

static void yajl_array_free(yajl_val v) {
    size_t i;

    if (!YAJL_IS_ARRAY(v)) return;

    for (i = 0; i < v->u.array.len; i++) {
        yajl_tree_free(v->u.array.values[i]);
        v->u.array.values[i] = NULL;
    }

    free(v->u.array.values);
    free(v);
}

/*
 * Parsing nested objects and arrays is implemented using a stack. When a new
 * object or array starts (a curly or a square opening bracket is read), an
 * appropriate value is pushed on the stack. When the end of the object is
 * reached (an appropriate closing bracket has been read), the value is popped
 * off the stack and added to the enclosing object using "context_add_value".
 */
static int context_push(context_t *ctx, yajl_val v) {
    stack_elem_t *stack;

    stack = malloc(sizeof(*stack));
    if (stack == NULL) RETURN_ERROR (ctx, ENOMEM, "Out of memory");
    memset(stack, 0, sizeof(*stack));

    assert ((ctx->stack == NULL)
            || YAJL_IS_OBJECT(v)
            || YAJL_IS_ARRAY(v));

    stack->value = v;
    stack->next = ctx->stack;
    ctx->stack = stack;

    return (0);
}

static yajl_val context_pop(context_t *ctx) {
    stack_elem_t *stack;
    yajl_val v;

    if (ctx->stack == NULL) RETURN_ERROR (ctx, NULL, "context_pop: "
                                                     "Bottom of stack reached prematurely");

    stack = ctx->stack;
    ctx->stack = stack->next;

    v = stack->value;

    free(stack);

    return (v);
}

static int object_add_keyval(context_t *ctx,
                             yajl_val obj, char *key, yajl_val value) {
    const char **tmpk;
    yajl_val *tmpv;

    /* We're checking for NULL in "context_add_value" or its callers. */
    assert (ctx != NULL);
    assert (obj != NULL);
    assert (key != NULL);
    assert (value != NULL);

    /* We're assuring that "obj" is an object in "context_add_value". */
    assert(YAJL_IS_OBJECT(obj));

    tmpk = realloc((void *) obj->u.object.keys, sizeof(*(obj->u.object.keys)) * (obj->u.object.len + 1));
    if (tmpk == NULL) RETURN_ERROR(ctx, ENOMEM, "Out of memory");
    obj->u.object.keys = tmpk;

    tmpv = realloc(obj->u.object.values, sizeof(*obj->u.object.values) * (obj->u.object.len + 1));
    if (tmpv == NULL) RETURN_ERROR(ctx, ENOMEM, "Out of memory");
    obj->u.object.values = tmpv;

    obj->u.object.keys[obj->u.object.len] = key;
    obj->u.object.values[obj->u.object.len] = value;
    obj->u.object.len++;

    return (0);
}

static int array_add_value(context_t *ctx,
                           yajl_val array, yajl_val value) {
    yajl_val *tmp;

    /* We're checking for NULL pointers in "context_add_value" or its
     * callers. */
    assert (ctx != NULL);
    assert (array != NULL);
    assert (value != NULL);

    /* "context_add_value" will only call us with array values. */
    assert(YAJL_IS_ARRAY(array));

    tmp = realloc(array->u.array.values,
                  sizeof(*(array->u.array.values)) * (array->u.array.len + 1));
    if (tmp == NULL) RETURN_ERROR(ctx, ENOMEM, "Out of memory");
    array->u.array.values = tmp;
    array->u.array.values[array->u.array.len] = value;
    array->u.array.len++;

    return 0;
}

/*
 * Add a value to the value on top of the stack or the "root" member in the
 * context if the end of the parsing process is reached.
 */
static int context_add_value(context_t *ctx, yajl_val v) {
    /* We're checking for NULL values in all the calling functions. */
    assert (ctx != NULL);
    assert (v != NULL);

    /*
     * There are three valid states in which this function may be called:
     *   - There is no value on the stack => This is the only value. This is the
     *     last step done when parsing a document. We assign the value to the
     *     "root" member and return.
     *   - The value on the stack is an object. In this case store the key on the
     *     stack or, if the key has already been read, add key and value to the
     *     object.
     *   - The value on the stack is an array. In this case simply add the value
     *     and return.
     */
    if (ctx->stack == NULL) {
        assert (ctx->root == NULL);
        ctx->root = v;
        return (0);
    } else if (YAJL_IS_OBJECT (ctx->stack->value)) {
        if (ctx->stack->key == NULL) {
            if (!YAJL_IS_STRING (v)) RETURN_ERROR (ctx, EINVAL, "context_add_value: "
                                                                "Object key is not a string (%#04x)",
                                                   v->type);

            ctx->stack->key = v->u.string;
            v->u.string = NULL;
            free(v);
            return (0);
        } else /* if (ctx->key != NULL) */
        {
            char *key;

            key = ctx->stack->key;
            ctx->stack->key = NULL;
            return (object_add_keyval(ctx, ctx->stack->value, key, v));
        }
    } else if (YAJL_IS_ARRAY (ctx->stack->value)) {
        return (array_add_value(ctx, ctx->stack->value, v));
    } else {
        RETURN_ERROR (ctx, EINVAL, "context_add_value: Cannot add value to "
                                   "a value of type %#04x (not a composite type)",
                      ctx->stack->value->type);
    }
}

static int handle_string(void *ctx,
                         const unsigned char *string, size_t string_length) {
    yajl_val v;

    v = value_alloc(yajl_t_string);
    if (v == NULL) RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");

    v->u.string = malloc(string_length + 1);
    if (v->u.string == NULL) {
        free(v);
        RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");
    }
    memcpy(v->u.string, string, string_length);
    v->u.string[string_length] = 0;

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_number(void *ctx, const char *string, size_t string_length) {
    yajl_val v;
    char *endptr;

    v = value_alloc(yajl_t_number);
    if (v == NULL) RETURN_ERROR((context_t *) ctx, STATUS_ABORT, "Out of memory");

    v->u.number.r = malloc(string_length + 1);
    if (v->u.number.r == NULL) {
        free(v);
        RETURN_ERROR((context_t *) ctx, STATUS_ABORT, "Out of memory");
    }
    memcpy(v->u.number.r, string, string_length);
    v->u.number.r[string_length] = 0;

    v->u.number.flags = 0;

    errno = 0;
    v->u.number.i = yajl_parse_integer((const unsigned char *) v->u.number.r,
                                       strlen(v->u.number.r));
    if (errno == 0)
        v->u.number.flags |= YAJL_NUMBER_INT_VALID;

    endptr = NULL;
    errno = 0;
    v->u.number.d = strtod(v->u.number.r, &endptr);
    if ((errno == 0) && (endptr != NULL) && (*endptr == 0))
        v->u.number.flags |= YAJL_NUMBER_DOUBLE_VALID;

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_start_map(void *ctx) {
    yajl_val v;

    v = value_alloc(yajl_t_object);
    if (v == NULL) RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");

    v->u.object.keys = NULL;
    v->u.object.values = NULL;
    v->u.object.len = 0;

    return ((context_push(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_end_map(void *ctx) {
    yajl_val v;

    v = context_pop(ctx);
    if (v == NULL)
        return (STATUS_ABORT);

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_start_array(void *ctx) {
    yajl_val v;

    v = value_alloc(yajl_t_array);
    if (v == NULL) RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");

    v->u.array.values = NULL;
    v->u.array.len = 0;

    return ((context_push(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_end_array(void *ctx) {
    yajl_val v;

    v = context_pop(ctx);
    if (v == NULL)
        return (STATUS_ABORT);

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_boolean(void *ctx, int boolean_value) {
    yajl_val v;

    v = value_alloc(boolean_value ? yajl_t_true : yajl_t_false);
    if (v == NULL) RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static int handle_null(void *ctx) {
    yajl_val v;

    v = value_alloc(yajl_t_null);
    if (v == NULL) RETURN_ERROR ((context_t *) ctx, STATUS_ABORT, "Out of memory");

    return ((context_add_value(ctx, v) == 0) ? STATUS_CONTINUE : STATUS_ABORT);
}

static const yajl_callbacks callbacks = {
        /* null        = */ handle_null,
        /* boolean     = */ handle_boolean,
        /* integer     = */ NULL,
        /* double      = */ NULL,
        /* number      = */ handle_number,
        /* string      = */ handle_string,
        /* start map   = */ handle_start_map,
        /* map key     = */ handle_string,
        /* end map     = */ handle_end_map,
        /* start array = */ handle_start_array,
        /* end array   = */ handle_end_array
};

/*
 * Public functions
 */
yajl_val yajl_tree_parse(const char *input,
                         char *error_buffer, size_t error_buffer_size) {


    yajl_handle handle;
    yajl_status status;
    char *internal_err_str;
    context_t ctx = {NULL, NULL, NULL, 0};

    ctx.errbuf = error_buffer;
    ctx.errbuf_size = error_buffer_size;

    if (error_buffer != NULL)
        memset(error_buffer, 0, error_buffer_size);

    handle = yajl_alloc(&callbacks, NULL, &ctx);
    yajl_config(handle, yajl_allow_comments, 1);

    status = yajl_parse(handle,
                        (unsigned char *) input,
                        strlen(input));
    status = yajl_complete_parse(handle);
    if (status != yajl_status_ok) {
        if (error_buffer != NULL && error_buffer_size > 0) {
            internal_err_str = (char *) yajl_get_error(handle, 1,
                                                       (const unsigned char *) input,
                                                       strlen(input));
            snprintf(error_buffer, error_buffer_size, "%s", internal_err_str);
            YA_FREE(&(handle->alloc), internal_err_str);
        }
        yajl_free(handle);
        return NULL;
    }

    yajl_free(handle);
    return (ctx.root);
}

yajl_val yajl_tree_parse_with_len (const char *input, size_t buf_len) {

    yajl_handle handle;
    yajl_status status;
    context_t ctx = {NULL, NULL, NULL, 0};

    handle = yajl_alloc(&callbacks, NULL, &ctx);
    yajl_config(handle, yajl_allow_comments, 1);

    yajl_parse(handle,
                        (unsigned char *) input,
                        buf_len);
    status = yajl_complete_parse(handle);
    if (status != yajl_status_ok) {
        yajl_free(handle);
        return NULL;
    }

    yajl_free(handle);
    return (ctx.root);
}

yajl_val yajl_tree_get(yajl_val n, const char **path, yajl_type type) {
    if (!path) return NULL;
    while (n && *path) {
        size_t i;
        size_t len;

        if (n->type != yajl_t_object) return NULL;
        len = n->u.object.len;
        for (i = 0; i < len; i++) {
            if (!strcmp(*path, n->u.object.keys[i])) {
                n = n->u.object.values[i];
                break;
            }
        }
        if (i == len) return NULL;
        path++;
    }
    if (n && type != yajl_t_any && type != n->type) n = NULL;
    return n;
}

yajl_val yajl_tree_get_field(yajl_val parent, const char *path, yajl_type type) {
    yajl_val n;
    size_t len, i;
    if (!parent || !path) {
        return NULL;
    }

    if (parent->type != yajl_t_object) return NULL;

    n = NULL;
    len = parent->u.object.len;
    for (i = 0; i < len; i++) {
        if (!strcmp(path, parent->u.object.keys[i])) {
            n = parent->u.object.values[i];
            break;
        }
    }
    if (i == len) return NULL;

    if (n && type != yajl_t_any && type != n->type) n = NULL;
    return n;
}

void yajl_tree_free(yajl_val v) {
    if (v == NULL) return;

    if (YAJL_IS_STRING(v)) {
        free(v->u.string);
        free(v);
    } else if (YAJL_IS_NUMBER(v)) {
        free(v->u.number.r);
        free(v);
    } else if (YAJL_GET_OBJECT(v)) {
        yajl_object_free(v);
    } else if (YAJL_GET_ARRAY(v)) {
        yajl_array_free(v);
    } else /* if (yajl_t_true or yajl_t_false or yajl_t_null) */
    {
        free(v);
    }
}

struct yajl_tree_parser_s {
    yajl_handle handle;
    context_t ctx;
};

yajl_tree_parser yajl_tree_alloc_parser() {
    yajl_alloc_funcs afs;
    yajl_tree_parser parser;

    yajl_set_default_alloc_funcs(&afs);

    parser = YA_MALLOC(&afs, sizeof(*parser));
    if (parser == NULL) {
        return NULL;
    }

    memset(parser, 0, sizeof(*parser));

    parser->handle = yajl_alloc(&callbacks, NULL, &parser->ctx);
    if (parser->handle == NULL) {
        YA_FREE(&afs, parser);
        return NULL;
    }

    return parser;
}

yajl_status yajl_tree_parse_incrementally(yajl_tree_parser parser,
                                          const char *input,
                                          size_t input_len) {
    return yajl_parse(parser->handle,
                      (unsigned char *) input,
                      input_len);
}

yajl_val yajl_tree_finish_get(yajl_tree_parser parser) {
    yajl_status status;

    status = yajl_complete_parse(parser->handle);
    if (status != yajl_status_ok) {
        return NULL;
    }

    return parser->ctx.root;
}

void yajl_tree_free_parser(yajl_tree_parser parser) {
    yajl_alloc_funcs afs;
    yajl_tree_free(parser->ctx.root);
    yajl_free(parser->handle);
    yajl_set_default_alloc_funcs(&afs);
    YA_FREE(&afs, parser);
}